//#define USE_CSHARP_ZLIB

#if USE_CSHARP_ZLIB
using MobileBaseCommon.zlib;
#endif
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

//#if UNITY_ANDROID && !UNITY_EDITOR
//using ZipUInt = System.UInt32;
//#else
//using ZipUInt = System.IntPtr;
//#endif
using ZipUInt = System.IntPtr;

//#define Z_OK            0

///* utility functions */

///*
//     The following utility functions are implemented on top of the basic
//   stream-oriented functions.  To simplify the interface, some default options
//   are assumed (compression level and memory usage, standard memory allocation
//   functions).  The source code of these utility functions can be modified if
//   you need special options.
//*/

//ZEXTERN int ZEXPORT compress OF((Bytef* dest, uLongf* destLen,
//                             const Bytef* source, uLong sourceLen));
///*
//     Compresses the source buffer into the destination buffer.  sourceLen is
//   the byte length of the source buffer.  Upon entry, destLen is the total size
//   of the destination buffer, which must be at least the value returned by
//   compressBound(sourceLen).  Upon exit, destLen is the actual size of the
//   compressed buffer.

//     compress returns Z_OK if success, Z_MEM_ERROR if there was not
//   enough memory, Z_BUF_ERROR if there was not enough room in the output
//   buffer.
//*/

//ZEXTERN int ZEXPORT compress2 OF((Bytef* dest, uLongf* destLen,
//                              const Bytef* source, uLong sourceLen,
//                              int level));
///*
//     Compresses the source buffer into the destination buffer.  The level
//   parameter has the same meaning as in deflateInit.  sourceLen is the byte
//   length of the source buffer.  Upon entry, destLen is the total size of the
//   destination buffer, which must be at least the value returned by
//   compressBound(sourceLen).  Upon exit, destLen is the actual size of the
//   compressed buffer.

//     compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
//   memory, Z_BUF_ERROR if there was not enough room in the output buffer,
//   Z_STREAM_ERROR if the level parameter is invalid.
//*/

//ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen));
///*
//     compressBound() returns an upper bound on the compressed size after
//   compress() or compress2() on sourceLen bytes.  It would be used before a
//   compress() or compress2() call to allocate the destination buffer.
//*/

//ZEXTERN int ZEXPORT uncompress OF((Bytef* dest, uLongf* destLen,
//                                   const Bytef* source, uLong sourceLen));
///*
//     Decompresses the source buffer into the destination buffer.  sourceLen is
//   the byte length of the source buffer.  Upon entry, destLen is the total size
//   of the destination buffer, which must be large enough to hold the entire
//   uncompressed data.  (The size of the uncompressed data must have been saved
//   previously by the compressor and transmitted to the decompressor by some
//   mechanism outside the scope of this compression library.) Upon exit, destLen
//   is the actual size of the uncompressed buffer.

//     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
//   enough memory, Z_BUF_ERROR if there was not enough room in the output
//   buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete.  In
//   the case where there is not enough room, uncompress() will fill the output
//   buffer with the uncompressed data up to that point.
//*/

namespace MobileBaseCommon.ZlibUtility
{
    public static class ZlipTool
    {
#if !USE_CSHARP_ZLIB
        // compression levels
        public const int Z_NO_COMPRESSION = 0;
        public const int Z_BEST_SPEED = 1;
        public const int Z_BEST_COMPRESSION = 9;
        public const int Z_DEFAULT_COMPRESSION = (-1);

        // compression strategy
        public const int Z_FILTERED = 1;
        public const int Z_HUFFMAN_ONLY = 2;
        public const int Z_DEFAULT_STRATEGY = 0;

        public const int Z_NO_FLUSH = 0;
        public const int Z_PARTIAL_FLUSH = 1;
        public const int Z_SYNC_FLUSH = 2;
        public const int Z_FULL_FLUSH = 3;
        public const int Z_FINISH = 4;

        public const int Z_OK = 0;
        public const int Z_STREAM_END = 1;
        public const int Z_NEED_DICT = 2;
        public const int Z_ERRNO = -1;
        public const int Z_STREAM_ERROR = -2;
        public const int Z_DATA_ERROR = -3;
        public const int Z_MEM_ERROR = -4;
        public const int Z_BUF_ERROR = -5;
        public const int Z_VERSION_ERROR = -6;

#if UNITY_IPHONE && !UNITY_EDITOR
        const string ZLibName = "__Internal";
#else
        const string ZLibName = "z";
#endif

        [DllImport(ZLibName, EntryPoint = "compress")]
        private static extern int compress(Byte[] dest, ref ZipUInt destLen, Byte[] source, ZipUInt sourceLen);

        [DllImport(ZLibName, EntryPoint = "compress2")]
        private static extern int compress2(Byte[] dest, ref ZipUInt destLen, Byte[] source, ZipUInt sourceLen, int level);

        [DllImport(ZLibName, EntryPoint = "compressBound")]
        private static extern ZipUInt compressBound(ZipUInt sourceLen);

        [DllImport(ZLibName, EntryPoint = "uncompress")]
        private static extern int uncompress(Byte[] dest, ref ZipUInt destLen, Byte[] source, ZipUInt sourceLen);

#endif

        public static byte[] ByteToZip(byte[] bytes)
        {
#if USE_CSHARP_ZLIB
            using (MemoryStream stmInput = new MemoryStream(bytes))
            {
                using (MemoryStream stmOutPut = CompressStream(stmInput))
                {
                    return stmOutPut.ToArray();
                }
            }
#else
            ZipUInt originLen = (ZipUInt)bytes.LongLength;
            ZipUInt zipLen = compressBound(originLen);
            byte[] zipBytes = new byte[(long)zipLen];

            compress(zipBytes, ref zipLen, bytes, originLen);

            byte[] outBytes = new byte[(long)zipLen];
            Array.Copy(zipBytes, outBytes, (long)zipLen);
            return outBytes;
#endif
        }

        public static byte[] ZipToByte(byte[] bytes, ref ulong originLen)
        {
#if USE_CSHARP_ZLIB
            using (MemoryStream stmInput = new MemoryStream(bytes))
            {
                using (MemoryStream stmOutPut = DecompressStream(stmInput))
                {
                    return stmOutPut.ToArray();
                }
            }
#else
            ZipUInt oriLen = (ZipUInt)originLen;
            ZipUInt zipLen = (ZipUInt)bytes.LongLength;
            byte[] dest = new byte[originLen];

            uncompress(dest, ref oriLen, bytes, zipLen);
            originLen = (ulong)oriLen;

            return dest;
#endif
        }

#if USE_CSHARP_ZLIB
        private static MemoryStream CompressStream(Stream SourceStream)
        {
            MemoryStream stmOutput = new MemoryStream();
            ZOutputStream outZStream = new ZOutputStream(stmOutput, zlibConst.Z_DEFAULT_COMPRESSION);
            CopyStream(SourceStream, outZStream);
            outZStream.finish();
            return stmOutput;
        }

        private static MemoryStream DecompressStream(Stream SourceStream)
        {
            MemoryStream stmOutput = new MemoryStream();
            ZOutputStream outZStream = new ZOutputStream(stmOutput);
            CopyStream(SourceStream, outZStream);
            outZStream.finish();
            return stmOutput;
        }

        private static void CopyStream(Stream input, Stream output)
        {
            byte[] buffer = new byte[512];
            int len;
            while ((len = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                output.Write(buffer, 0, len);
            }
            output.Flush();
        }
#endif
    }
}
